// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MEDIA_GPU_AVDA_CODEC_ALLOCATOR_H_
#define MEDIA_GPU_AVDA_CODEC_ALLOCATOR_H_

#include <stddef.h>

#include <memory>

#include "base/android/build_info.h"
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/optional.h"
#include "base/synchronization/waitable_event.h"
#include "base/sys_info.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "base/time/tick_clock.h"
#include "base/trace_event/trace_event.h"
#include "media/base/android/media_drm_bridge_cdm_context.h"
#include "media/base/android/sdk_media_codec_bridge.h"
#include "media/base/media.h"
#include "media/base/surface_manager.h"
#include "media/base/video_codecs.h"
#include "media/gpu/media_gpu_export.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gl/android/scoped_java_surface.h"

namespace media {

// For TaskRunnerFor. These are used as vector indices, so please update
// AVDACodecAllocator's constructor if you add / change them.
enum TaskType {
    // Task for an autodetected MediaCodec instance.
    AUTO_CODEC = 0,

    // Task for a software-codec-required MediaCodec.
    SW_CODEC = 1,
};

// Configuration info for MediaCodec.
// This is used to shuttle configuration info between threads without needing
// to worry about the lifetime of the AVDA instance.
class CodecConfig : public base::RefCountedThreadSafe<CodecConfig> {
public:
    CodecConfig();

    VideoCodec codec = kUnknownVideoCodec;

    // The surface that MediaCodec is configured to output to.
    gl::ScopedJavaSurface surface;
    int surface_id = SurfaceManager::kNoSurfaceID;

    // The SurfaceTexture attached to |surface|, or nullptr if |surface| is
    // SurfaceView backed.
    scoped_refptr<gl::SurfaceTexture> surface_texture;

    // The MediaCrypto that MediaCodec is configured with for an encrypted stream.
    MediaDrmBridgeCdmContext::JavaObjectPtr media_crypto;

    // Whether the encryption scheme requires us to use a protected surface.
    bool needs_protected_surface = false;

    // The initial coded size. The actual size might change at any time, so this
    // is only a hint.
    gfx::Size initial_expected_coded_size;

    // The type of allocation to use for. We use this to select the right thread
    // for construction / destruction, and to decide if we should restrict the
    // codec to be software only.
    TaskType task_type;

    // Codec specific data (SPS and PPS for H264).
    std::vector<uint8_t> csd0;
    std::vector<uint8_t> csd1;

protected:
    friend class base::RefCountedThreadSafe<CodecConfig>;
    virtual ~CodecConfig();

private:
    DISALLOW_COPY_AND_ASSIGN(CodecConfig);
};

class AVDACodecAllocatorClient {
public:
    // Called when the requested SurfaceView becomes available after a call to
    // AllocateSurface()
    virtual void OnSurfaceAvailable(bool success) = 0;

    // Called when the allocated surface is being destroyed. This must either
    // replace the surface with MediaCodec#setSurface, or release the MediaCodec
    // it's attached to. The client no longer owns the surface and doesn't
    // need to call DeallocateSurface();
    virtual void OnSurfaceDestroyed() = 0;

    // Called on the main thread when a new MediaCodec is configured.
    // |media_codec| will be null if configuration failed.
    virtual void OnCodecConfigured(
        std::unique_ptr<VideoCodecBridge> media_codec)
        = 0;

protected:
    ~AVDACodecAllocatorClient() { }
};

// AVDACodecAllocator manages threads for allocating and releasing MediaCodec
// instances.  These activities can hang, depending on android version, due
// to mediaserver bugs.  AVDACodecAllocator detects these cases, and reports
// on them to allow software fallback if the HW path is hung up.
class MEDIA_GPU_EXPORT AVDACodecAllocator {
public:
    static AVDACodecAllocator* Instance();

    // Called synchronously when the given surface is being destroyed on the
    // browser UI thread.
    void OnSurfaceDestroyed(int surface_id);

    // Make sure the construction threads are started for |client|. Returns true
    // on success.
    bool StartThread(AVDACodecAllocatorClient* client);

    void StopThread(AVDACodecAllocatorClient* client);

    // Returns true if the caller now owns the surface, or false if someone else
    // owns the surface. |client| will be notified when the surface is available
    // via OnSurfaceAvailable().
    bool AllocateSurface(AVDACodecAllocatorClient* client, int surface_id);

    // Relinquish ownership of the surface or stop waiting for it to be available.
    // The caller must guarantee that when calling this the surface is either no
    // longer attached to a MediaCodec, or the MediaCodec it was attached to is
    // was released with ReleaseMediaCodec().
    void DeallocateSurface(AVDACodecAllocatorClient* client, int surface_id);

    // Create and configure a MediaCodec synchronously.
    std::unique_ptr<VideoCodecBridge> CreateMediaCodecSync(
        scoped_refptr<CodecConfig> codec_config);

    // Create and configure a MediaCodec asynchronously. The result is delivered
    // via OnCodecConfigured().
    void CreateMediaCodecAsync(base::WeakPtr<AVDACodecAllocatorClient> client,
        scoped_refptr<CodecConfig> codec_config);

    // Asynchronously release |media_codec| with the attached surface.
    // TODO(watk): Bundle the MediaCodec and surface together so you can't get
    // this pairing wrong.
    void ReleaseMediaCodec(std::unique_ptr<VideoCodecBridge> media_codec,
        TaskType task_type,
        int surface_id);

    // Returns a hint about whether the construction thread has hung for
    // |task_type|.  Note that if a thread isn't started, then we'll just return
    // "not hung", since it'll run on the current thread anyway.  The hang
    // detector will see no pending jobs in that case, so it's automatic.
    bool IsThreadLikelyHung(TaskType task_type);

    // Return true if and only if there is any AVDA registered.
    bool IsAnyRegisteredAVDA();

    // Return the task type to use for a new codec allocation, or nullopt if
    // both threads are hung.
    base::Optional<TaskType> TaskTypeForAllocation();

    // Return the task runner for tasks of type |type|.
    scoped_refptr<base::SingleThreadTaskRunner> TaskRunnerFor(TaskType task_type);

    // Return a reference to the thread for unit tests.
    base::Thread& GetThreadForTesting(TaskType task_type);

private:
    friend struct base::DefaultLazyInstanceTraits<AVDACodecAllocator>;
    friend class AVDACodecAllocatorTest;

    struct OwnerRecord {
        AVDACodecAllocatorClient* owner = nullptr;
        AVDACodecAllocatorClient* waiter = nullptr;
    };

    class HangDetector : public base::MessageLoop::TaskObserver {
    public:
        HangDetector(base::TickClock* tick_clock);
        void WillProcessTask(const base::PendingTask& pending_task) override;
        void DidProcessTask(const base::PendingTask& pending_task) override;
        bool IsThreadLikelyHung();

    private:
        base::Lock lock_;

        // Non-null when a task is currently running.
        base::TimeTicks task_start_time_;

        base::TickClock* tick_clock_;

        DISALLOW_COPY_AND_ASSIGN(HangDetector);
    };

    // Handy combination of a thread and hang detector for it.
    struct ThreadAndHangDetector {
        ThreadAndHangDetector(const std::string& name, base::TickClock* tick_clock)
            : thread(name)
            , hang_detector(tick_clock)
        {
        }
        base::Thread thread;
        HangDetector hang_detector;
    };

    // |tick_clock| and |stop_event| are for tests only.
    AVDACodecAllocator(base::TickClock* tick_clock = nullptr,
        base::WaitableEvent* stop_event = nullptr);
    ~AVDACodecAllocator();

    void OnMediaCodecAndSurfaceReleased(int surface_id);

    // Stop the thread indicated by |index|. This signals stop_event_for_testing_
    // after both threads are stopped.
    void StopThreadTask(size_t index);

    // All registered AVDAs.
    std::set<AVDACodecAllocatorClient*> clients_;

    // Indexed by surface id.
    std::map<int32_t, OwnerRecord> surface_owners_;

    // Waitable events for ongoing release tasks indexed by surface id so we can
    // wait on the codec release if the surface attached to it is being destroyed.
    std::map<int32_t, base::WaitableEvent> pending_codec_releases_;

    // Threads for each of TaskType.  They are started / stopped as avda instances
    // show and and request them.  The vector indicies must match TaskType.
    std::vector<ThreadAndHangDetector*> threads_;

    base::ThreadChecker thread_checker_;

    base::WaitableEvent* stop_event_for_testing_;

    // For canceling pending StopThreadTask()s.
    base::WeakPtrFactory<AVDACodecAllocator> weak_this_factory_;

    DISALLOW_COPY_AND_ASSIGN(AVDACodecAllocator);
};

} // namespace media

#endif // MEDIA_GPU_AVDA_CODEC_ALLOCATOR_H_
